package com.cscec81.passport.base.oauth2.server.authorization.domain;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;

public class PassportUser implements UserDetails, CredentialsContainer {
  private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

  private String userId;

  private String password;

  private String username;

  private Set<GrantedAuthority> authorities;

  private boolean accountNonExpired;

  private boolean accountNonLocked;

  private boolean credentialsNonExpired;

  private boolean enabled;

  private Set<String> roles;

  private String employeeId;

  private String avatar;

  public PassportUser() {
  }

  /**
   * Calls the more complex constructor with all boolean arguments set to {@code true}.
   */
  public PassportUser(String userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
    this(userId, username, password, authorities, null);
  }

  /**
   * Calls the more complex constructor with all boolean arguments set to {@code true}.
   */
  public PassportUser(String userId, String username, String password, Collection<? extends GrantedAuthority> authorities, Set<String> roles) {
    this(userId, username, password, true, true, true, true, authorities, roles, null, null);
  }

  public PassportUser(String userId, String username, String password, boolean enabled, boolean accountNonExpired,
                       boolean credentialsNonExpired, boolean accountNonLocked,
                       Collection<? extends GrantedAuthority> authorities, Set<String> roles, String employeeId, String avatar) {
    Assert.isTrue(username != null && !"".equals(username) && password != null,
      "Cannot pass null or empty values to constructor");
    this.userId = userId;
    this.username = username;
    this.password = password;
    this.enabled = enabled;
    this.accountNonExpired = accountNonExpired;
    this.credentialsNonExpired = credentialsNonExpired;
    this.accountNonLocked = accountNonLocked;
    this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    this.roles = CollectionUtils.isNotEmpty(roles) ? roles : new HashSet<>();
    this.employeeId = employeeId;
    this.avatar = avatar;
  }

  @Override
  public Collection<GrantedAuthority> getAuthorities() {
    return this.authorities;
  }

  @Override
  public String getPassword() {
    return this.password;
  }

  @Override
  public String getUsername() {
    return this.username;
  }

  @Override
  public boolean isEnabled() {
    return this.enabled;
  }

  @Override
  public boolean isAccountNonExpired() {
    return this.accountNonExpired;
  }

  @Override
  public boolean isAccountNonLocked() {
    return this.accountNonLocked;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return this.credentialsNonExpired;
  }

  @Override
  public void eraseCredentials() {
    this.password = null;
  }

  public String getUserId() {
    return userId;
  }

  public Set<String> getRoles() {
    return roles;
  }

  public String getEmployeeId() {
    return employeeId;
  }

  public String getAvatar() {
    return avatar;
  }

  private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
    Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
    // Ensure array iteration order is predictable (as per
    // UserDetails.getAuthorities() contract and SEC-717)
    SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new PassportUser.AuthorityComparator());
    for (GrantedAuthority grantedAuthority : authorities) {
      Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
      sortedAuthorities.add(grantedAuthority);
    }
    return sortedAuthorities;
  }

  private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    @Override
    public int compare(GrantedAuthority g1, GrantedAuthority g2) {
      // Neither should ever be null as each entry is checked before adding it to
      // the set. If the authority is null, it is a custom authority and should
      // precede others.
      if (g2.getAuthority() == null) {
        return -1;
      }
      if (g1.getAuthority() == null) {
        return 1;
      }
      return g1.getAuthority().compareTo(g2.getAuthority());
    }

  }
}
